package com.author.surface;

import javax.swing.JSlider;
import javax.swing.JTextPane;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.text.AttributeSet;
import javax.swing.text.BoxView;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.MutableAttributeSet;
import javax.swing.text.ParagraphView;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyledDocument;
import javax.swing.text.View;

import com.author.document.model.ExtendedDocument;
import com.author.document.view.ExtendedParagraphView;

public class CustomSlider extends JSlider {
	/** Minimum and maximum values of the slider */
	public static final int	SLIDER_MINIMUM			= 0;
	public static final int	SLIDER_MAXIMUM			= 100;

	/** Initial and final position of the slider */
	public static final int	SLIDER_INITIAL_POSITION	= 3;
	public static final int	SLIDER_FINAL_POSITION	= 97;

	/** Major and minor tick spacing for the custom slider */
	public static final int	MAJOR_TICK_SPACING		= 5;
	public static final int	MINOR_TICK_SPACING		= 1;
	/** Extent of the slider */
	public static final int	SLIDER_EXTENT			= 1;

	/** Change listener to listen to slider knob dragging by the user */
	private ChangeListener	positionChangeListener;
	/** Text Pane on which the custom slider will act as a Ruler */
	private JTextPane		textpane;

	/**
	 * The following flag indicates if a response is needed when the slider
	 * changes its position. This trick is needed because sometimes the knob of
	 * the slider is dragged by the UI updater and in those cases we do not want
	 * any change event listener to respond
	 */
	private boolean			isRespNeeded			= true;

	public CustomSlider() {
		/* Set all UI related properties of the Custom slider */
		setMajorTickSpacing(MAJOR_TICK_SPACING);
		setMinorTickSpacing(MINOR_TICK_SPACING);
		setPaintTicks(true);
		setPaintTrack(true);
		setSnapToTicks(true);

		/* Set initial values of the slider */
		setMinimum(SLIDER_MINIMUM);
		setMaximum(SLIDER_MAXIMUM);
		setValue(SLIDER_INITIAL_POSITION);
		setExtent(SLIDER_EXTENT);

		/* Add change listener to the Custom Slider */
		this.positionChangeListener = new PositionChangeListener();
		this.addChangeListener(this.positionChangeListener);
	}

	/**
	 * @param textpane
	 *            to which the custom slider should be attached to
	 */
	public void setTextPane(JTextPane textpane) {
		if (textpane == null) {
			throw new RuntimeException("Null reference of text pane."
					+ "Slider instance can't be attached");
		}
		this.textpane = textpane;
	}

	/**
	 * @param modelResp
	 *            , flag which indicates to the model if a res
	 */
	public void setChangeEventResponse(boolean Resp) {
		this.isRespNeeded = Resp;
	}

	/*
	 * Shift the element in the Text Pane by an amount given by the difference
	 * between the ending and the starting position
	 */
	protected void shiftTextPaneElement(int start, int end) {
		if (start == end) {
			return;
		}

		/*
		 * While shifting the text pane element we need to ensure that in case
		 * there is a selection - all the paragraph elements that are currently
		 * under selection are shifted.
		 */
		int dot = this.textpane.getCaret().getDot();
		int mark = this.textpane.getCaret().getMark();
		int pos = mark <= dot ? mark : dot;
		int range = Math.abs(dot - mark);

		/* Get paragraph element at the current position indicated by pos */
		StyledDocument styledDocument = (StyledDocument) TextPane
				.getTextPaneDocument();
		if (!(styledDocument instanceof ExtendedDocument)) {
			return;
		}
		Element elem = (Element) ((ExtendedDocument) styledDocument)
				.getParagraphElement(pos);
		SimpleAttributeSet sas = new SimpleAttributeSet(elem.getAttributes()
				.copyAttributes());

		/* Add attribute for setting the left inset of the paragraph */
		float left_inset = 0;
		if (sas.getAttribute(ExtendedDocument.PARAGRAPH_LEFT_INSET) != null) {
			left_inset = (Float) sas
					.getAttribute(ExtendedDocument.PARAGRAPH_LEFT_INSET);
			sas.removeAttribute(ExtendedDocument.PARAGRAPH_LEFT_INSET);
		}

		if (end != CustomSlider.SLIDER_INITIAL_POSITION) {
			if (left_inset != 0) {
				sas.addAttribute(
						ExtendedDocument.PARAGRAPH_LEFT_INSET,
						left_inset
								+ ((end - start) * ExtendedDocument.CHARACTER_WIDTH));
			} else {
				sas.addAttribute(ExtendedDocument.PARAGRAPH_LEFT_INSET,
						(end - start) * ExtendedDocument.CHARACTER_WIDTH);
			}
		} else {
			/*
			 * If the slider knob has been dragged to the initial position then
			 * reset left inset to zero
			 */
			sas.addAttribute(ExtendedDocument.PARAGRAPH_LEFT_INSET, (float) 0.0);
		}

		/* Remove attribute before adding */
		sas.removeAttribute(ExtendedDocument.PARAGRAPH_LEFT_INDENT);
		sas.addAttribute(ExtendedDocument.PARAGRAPH_LEFT_INDENT, end);

		/* Set left inset attribute for the entire selection */
		styledDocument.setParagraphAttributes(pos, range, sas, false);
	}

	/**
	 * Implement behavior that will respond to the user dragging the knob of the
	 * custom slider. This will result in the paragraph which contains the caret
	 * to be shifted to the left/right
	 */
	protected class PositionChangeListener implements ChangeListener {
		/* Start and end positions of the slider knob */
		private int	start	= -1;
		private int	end		= -1;

		@Override
		public void stateChanged(ChangeEvent ce) {
			if (!CustomSlider.this.isRespNeeded) {
				/* No response is needed */
				CustomSlider.this.isRespNeeded = true;
				return;
			}
			if (CustomSlider.this.getValueIsAdjusting()) {
				if (start == -1) {
					start = CustomSlider.this.getValue();
					end = -1;
				}
			} else {
				end = CustomSlider.this.getValue();
				/* Ensure that the slider does not go past the final positions */
				if (end < CustomSlider.SLIDER_INITIAL_POSITION) {
					end = CustomSlider.SLIDER_INITIAL_POSITION;
					CustomSlider.this.setValue(end);
					/* Ensure that a response is not generated due to this */
					CustomSlider.this.isRespNeeded = false;
				}
				if (end > CustomSlider.SLIDER_FINAL_POSITION) {
					end = CustomSlider.SLIDER_FINAL_POSITION;
					CustomSlider.this.setValue(end);
					/* Ensure that a response is not generated due to this */
					CustomSlider.this.isRespNeeded = false;
				}

				/*
				 * Shift the current element (the element inside the text pane
				 * which contains the caret) by a delta amount which is given by
				 * the difference between the starting and the ending position
				 * of the knob of the slider
				 */
				CustomSlider.this.shiftTextPaneElement(start, end);
				start = -1;
			}
		}
	}
}
